package com.itc.bpm.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itc.bpm.api.util.validation.ValidationUtils;
import com.itc.bpm.bean.BpmFormDesign;
import com.itc.bpm.bean.BpmModel;
import com.itc.bpm.dto.BpmModelCreateDto;
import com.itc.bpm.framework.factory.BpmModelUtil;
import com.itc.bpm.framework.factory.BpmServiceFactory;
import com.itc.bpm.framework.factory.vo.BpmModelUpdateReqVO;
import com.itc.bpm.mapper.BpmModelMapper;
import com.itc.bpm.service.BpmModelService;
import com.itc.bpm.util.FlowableUtils;
import com.itc.bpm.util.JsonUtils;
import com.itc.common.core.domain.R;
import com.itc.common.core.utils.StringUtils;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLUnit;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.bpmn.parser.BpmnParse;
import org.flowable.engine.impl.bpmn.parser.BpmnParser;
import org.flowable.engine.impl.persistence.entity.ModelEntity;
import org.flowable.engine.repository.Model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;

import javax.annotation.Resource;
import javax.validation.Valid;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class BpmModelServiceImpl extends ServiceImpl<BpmModelMapper, BpmModel> implements BpmModelService {

    @Resource
    protected RepositoryService repositoryService;


    @Autowired(required = false)
    BpmModelMapper bpmModelMapper;

    @Autowired(required = false)
    BpmModelUtil bpmModelUtil;

    @Transactional
    @Override
    public R addModel(BpmModel bpmModel) {
        if(bpmModel.getId()==null){
            // 校验名称编码是否重复
            LambdaQueryWrapper<BpmModel> queryWrapper = new LambdaQueryWrapper<BpmModel>().eq(BpmModel::getBpmKey,bpmModel.getBpmKey()).eq(BpmModel::getDelFlag,0);
            List<BpmModel> list1 =bpmModelMapper.selectList(queryWrapper);
            if(list1!=null && list1.size()>0){
                return R.fail("流程key不能重复");
            }
            queryWrapper = new LambdaQueryWrapper<BpmModel>().eq(BpmModel::getBpmName,bpmModel.getBpmName()).eq(BpmModel::getDelFlag,0);
            list1 =bpmModelMapper.selectList(queryWrapper);
            if(list1!=null && list1.size()>0){
                return R.fail("流程名称不能重复");
            }
            bpmModel.setDelFlag(0);
            bpmModel.setStatus(0);
            bpmModel.setVersion("1.0");
            bpmModel.setIsDeploy(0);
            if(StringUtils.isNotEmpty(bpmModel.getBpmnXml())){
                BpmModelCreateDto bpmModelCreateDto = new BpmModelCreateDto();
                bpmModelCreateDto.setName(bpmModel.getBpmName());
                bpmModelCreateDto.setKey(bpmModel.getBpmKey());
                bpmModelCreateDto.setDescription(bpmModel.getDescription());
                String delployId = createModel(bpmModelCreateDto,bpmModel.getBpmnXml());
                bpmModel.setDeployId(delployId);
                bpmModel.setFormCode(getXmlTaskForm(bpmModel.getBpmnXml()));
              //  bpmModel.setDeptChild(getXmlTaskDeptChild(bpmModel.getBpmnXml()));
            }
            bpmModelMapper.insert(bpmModel);

        }else { //存在进行修改
            BpmModel oldModel = bpmModelMapper.selectById(bpmModel.getId());
            if(oldModel!=null){
                if(!oldModel.getBpmKey().equals(bpmModel.getBpmKey())){
                   return R.fail("流程key不能进行修改");
                }
                // 历史数据没有生成xml，则直接更新xml 为第一版
                if(StringUtils.isEmpty(oldModel.getBpmnXml())){
                    if(StringUtils.isNotEmpty(bpmModel.getBpmnXml())){
                        BpmModelCreateDto bpmModelCreateDto = new BpmModelCreateDto();
                        bpmModelCreateDto.setName(bpmModel.getBpmName());
                        bpmModelCreateDto.setKey(bpmModel.getBpmKey());
                        bpmModelCreateDto.setDescription(bpmModel.getDescription());
                        String delployId = createModel(bpmModelCreateDto,bpmModel.getBpmnXml());
                        bpmModel.setDeployId(delployId);

                        bpmModel.setFormCode(getXmlTaskForm(bpmModel.getBpmnXml()));
                       // bpmModel.setDeptChild(getXmlTaskDeptChild(bpmModel.getBpmnXml()));
                    }
                    bpmModelMapper.updateById(bpmModel);
                }
                // 历史xml 不为空，比较两次xml 是否产生变化，变化新增版本，标记历史版本，否则直接更新不需要变更版本信息
                else if(xmlStringsEqual(oldModel.getBpmnXml(),bpmModel.getBpmnXml())){// 相同没有变化，不进行版本升级
                    bpmModelMapper.updateById(bpmModel);
                }else{ // xml 内容不同
                    bpmModelMapper.updateHistoryForm(bpmModel.getBpmKey());
                    bpmModel.setVersion(getLastVersion(bpmModel.getBpmKey()));
                    bpmModel.setId(null);
                    bpmModel.setCreateTime(null);
                    bpmModel.setUpdateTime(null);
                    bpmModel.setStatus(0);
                    bpmModel.setIsDeploy(0);
                    bpmModel.setDelFlag(0);
                    bpmModel.setBpmKey(oldModel.getBpmKey());
                    bpmModel.setDeployId(oldModel.getDeployId());
                    if(oldModel!=null && StringUtils.isEmpty(oldModel.getDeployId())){
                        BpmModelCreateDto bpmModelCreateDto = new BpmModelCreateDto();
                        bpmModelCreateDto.setName(bpmModel.getBpmName());
                        bpmModelCreateDto.setKey(bpmModel.getBpmKey());
                        bpmModelCreateDto.setDescription(bpmModel.getDescription());
                        String delployId = createModel(bpmModelCreateDto,bpmModel.getBpmnXml());
                        bpmModel.setDeployId(delployId);
                    }
                    bpmModel.setFormCode(getXmlTaskForm(bpmModel.getBpmnXml()));
                   // bpmModel.setDeptChild(getXmlTaskDeptChild(bpmModel.getBpmnXml()));
                    bpmModelMapper.insert(bpmModel);
                }
                if(StringUtils.isNotEmpty(bpmModel.getBpmnXml())){
                    BpmModelUpdateReqVO updateReqVO = new BpmModelUpdateReqVO();
                    updateReqVO.setId(oldModel.getDeployId());
                    updateReqVO.setBpmnXml(bpmModel.getBpmnXml());
                    updateReqVO.setName(bpmModel.getBpmName());
                    updateReqVO.setFormType(20);
                    updateReqVO.setCategory("oa");
                    updateReqVO.setDescription(bpmModel.getDescription());
                    updateReqVO.setFormCustomViewPath("/bpm");
                    updateReqVO.setFormCustomCreatePath("/bpm");
                    bpmModelUtil.updateModel(updateReqVO);
                }
            }

        }

        return R.ok();
    }

    @Transactional
    @Override
    public R deployModel(Long id) {
        BpmModel bpmModel = this.getById(id);
        if(StringUtils.isNotEmpty(bpmModel.getBpmnXml()) && StringUtils.isNotEmpty(bpmModel.getDeployId())){
            bpmModelUtil.deployModel(bpmModel.getDeployId());
        }else{
            return R.fail("流程设计文件不存在，请设计后重新部署!");
        }
        // 记录部署状态、部署时间等信息
        bpmModel.setIsDeploy(1);
        bpmModel.setDeployDate(new Date());
        this.updateById(bpmModel);
        return R.ok("部署成功!");
    }


    @Transactional(rollbackFor = Exception.class)
    public String createModel(@Valid BpmModelCreateDto createReqVO, String bpmnXml) {
        checkKeyNCName(createReqVO.getKey());
        // 校验流程标识已经存在
        Model keyModel = getModelByKey(createReqVO.getKey());
        if (keyModel != null) {
            throw new RuntimeException("流程标识已存在");
        }

        // 创建流程定义
        Model model = repositoryService.newModel();
        model.setKey(createReqVO.getKey());
        model.setName(createReqVO.getName());
        // 保存流程定义
        repositoryService.saveModel(model);
        // 保存 BPMN XML
        saveModelBpmnXml(model, bpmnXml);
        return model.getId();
    }

    private void saveModelBpmnXml(Model model, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
    }
    private void checkKeyNCName(String key){
        if (!ValidationUtils.isXmlNCName(key)) {
            throw new RuntimeException("流程模型不存在");
        }
    }

    private Model getModelByKey(String key) {
        return repositoryService.createModelQuery().modelKey(key).singleResult();
    }

    public String getLastVersion(String code){
        LambdaQueryWrapper<BpmModel> queryWrapper = new LambdaQueryWrapper<BpmModel>().eq(BpmModel::getBpmKey,code).orderByDesc(BpmModel::getCreateTime).last("limit 1");
        BpmModel bpmFormDesign = bpmModelMapper.selectOne(queryWrapper);
        Integer num = 1;
        if(StringUtils.isNotEmpty(bpmFormDesign.getVersion())){
            float tempVaersion = Float.parseFloat(bpmFormDesign.getVersion());
            num = (int) tempVaersion;
            num++;
        }
        return num+".0";
    }

    public static boolean xmlStringsEqual(String xml1, String xml2){
        try{
            XMLUnit.setIgnoreWhitespace(true);
            XMLUnit.setIgnoreAttributeOrder(true);
            XMLUnit.setIgnoreComments(true);
            Diff myDiff = new Diff(xml1, xml2);
            return myDiff.similar();
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    public static Document stringToDocument(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(xml);
    }

    public static String documentToString(Document doc) throws Exception {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        transformer.transform(new DOMSource(doc), new StreamResult(bos));
        return bos.toString();
    }

    public static   String getXmlTaskForm(String xmlString){
        Collection<Task> userTasks = FlowableUtils.getAllUserTaskEvent(xmlString);
        String formCode = "";
        for(Task task:userTasks ){
            Map<String,List<ExtensionAttribute>> atts = task.getAttributes();
            if(atts.containsKey("formCode") && atts.get("formCode")!=null){
                String text = atts.get("formCode").get(0).getValue();
                if(StringUtils.isNotEmpty(text)){
                    formCode = formCode+text+",";
                }
            }
        }
        return formCode;
    }

    public static   String getXmlSeqFlowForm(String xmlString){
        Collection<SequenceFlow> userTasks = FlowableUtils.getAllConditionSequenceFlowEvent(xmlString);
        for(SequenceFlow item:userTasks){
            Set<String> extensionElements = item.getExtensionElements().keySet();
            System.out.println(JsonUtils.objectToJson(extensionElements));
            Map<String,List<ExtensionAttribute>> atts  = item.getAttributes();
        }
        return "";
    }

    public static   String getXmlTaskDeptChild(String xmlString){
        Collection<Task> userTasks = FlowableUtils.getAllUserTaskEvent(xmlString);
        String formCode = "";
        for(Task task:userTasks ){
            Map<String,List<ExtensionAttribute>> atts = task.getAttributes();
            if(atts.containsKey("dealManagePosition") && atts.get("dealManagePosition")!=null){
                String text = atts.get("dealManagePosition").get(0).getValue();
                if(StringUtils.isNotEmpty(text)){
                    formCode = formCode+text+",";
                }
            }
        }
        return formCode;
    }

    public static void main(String[] args) {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<definitions xmlns=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:flowable=\"http://flowable.org/bpmn\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:omgdc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:omgdi=\"http://www.omg.org/spec/DD/20100524/DI\" xmlns:bioc=\"http://bpmn.io/schema/bpmn/biocolor/1.0\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" targetNamespace=\"http://www.flowable.org/processdef\" exporter=\"Flowable Open Source Modeler\" exporterVersion=\"6.8.0\">\n" +
                "  <process id=\"process_2pt3ydud\" name=\"zzxtest001\" flowable:processCategory=\"11\" flowable:createBy=\"001\">\n" +
                "    <documentation>001</documentation>\n" +
                "    <startEvent id=\"startNode1\" name=\"开始\">\n" +
                "      <outgoing>Flow_1owh1r3</outgoing>\n" +
                "    </startEvent>\n" +
                "    <userTask id=\"Activity_0q9xxpo\" name=\"审核\">\n" +
                "      <incoming>Flow_1owh1r3</incoming>\n" +
                "      <outgoing>Flow_141y7vb</outgoing>\n" +
                "    </userTask>\n" +
                "    <sequenceFlow id=\"Flow_1owh1r3\" sourceRef=\"startNode1\" targetRef=\"Activity_0q9xxpo\" />\n" +
                "    <exclusiveGateway id=\"Gateway_1mknj2u\">\n" +
                "      <incoming>Flow_141y7vb</incoming>\n" +
                "      <outgoing>Flow_0x6jtcn</outgoing>\n" +
                "      <outgoing>Flow_11euysf</outgoing>\n" +
                "    </exclusiveGateway>\n" +
                "    <sequenceFlow id=\"Flow_141y7vb\" sourceRef=\"Activity_0q9xxpo\" targetRef=\"Gateway_1mknj2u\" />\n" +
                "    <userTask id=\"Activity_0mrl5t4\" name=\"复核\" flowable:isFinishRepeat=\"1821111992294387714\" flowable:formCode=\"form8e4ec64ef5144795aa27ce7f2498370b\" flowable:responseTime=\"null\">\n" +
                "      <incoming>Flow_0x6jtcn</incoming>\n" +
                "      <outgoing>Flow_1cknhix</outgoing>\n" +
                "    </userTask>\n" +
                "    <sequenceFlow id=\"Flow_0x6jtcn\" sourceRef=\"Gateway_1mknj2u\" targetRef=\"Activity_0mrl5t4\" />\n" +
                "    <userTask id=\"Activity_0u7jnud\" name=\"审查\">\n" +
                "      <incoming>Flow_11euysf</incoming>\n" +
                "      <outgoing>Flow_0zb2v9m</outgoing>\n" +
                "    </userTask>\n" +
                "    <sequenceFlow id=\"Flow_11euysf\" sourceRef=\"Gateway_1mknj2u\" targetRef=\"Activity_0u7jnud\" />\n" +
                "    <endEvent id=\"Event_0u2j1il\" name=\"结束\">\n" +
                "      <incoming>Flow_1cknhix</incoming>\n" +
                "      <incoming>Flow_0zb2v9m</incoming>\n" +
                "    </endEvent>\n" +
                "    <sequenceFlow id=\"Flow_1cknhix\" sourceRef=\"Activity_0mrl5t4\" targetRef=\"Event_0u2j1il\" />\n" +
                "    <sequenceFlow id=\"Flow_0zb2v9m\" sourceRef=\"Activity_0u7jnud\" targetRef=\"Event_0u2j1il\" />\n" +
                "  </process>\n" +
                "  <bpmndi:BPMNDiagram id=\"BPMNDiagram_flow\">\n" +
                "    <bpmndi:BPMNPlane id=\"BPMNPlane_flow\" bpmnElement=\"process_2pt3ydud\">\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_0zb2v9m_di\" bpmnElement=\"Flow_0zb2v9m\">\n" +
                "        <omgdi:waypoint x=\"680\" y=\"330\" />\n" +
                "        <omgdi:waypoint x=\"711\" y=\"330\" />\n" +
                "        <omgdi:waypoint x=\"711\" y=\"215\" />\n" +
                "        <omgdi:waypoint x=\"742\" y=\"215\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_1cknhix_di\" bpmnElement=\"Flow_1cknhix\">\n" +
                "        <omgdi:waypoint x=\"680\" y=\"215\" />\n" +
                "        <omgdi:waypoint x=\"742\" y=\"215\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_11euysf_di\" bpmnElement=\"Flow_11euysf\">\n" +
                "        <omgdi:waypoint x=\"500\" y=\"240\" />\n" +
                "        <omgdi:waypoint x=\"500\" y=\"330\" />\n" +
                "        <omgdi:waypoint x=\"580\" y=\"330\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_0x6jtcn_di\" bpmnElement=\"Flow_0x6jtcn\">\n" +
                "        <omgdi:waypoint x=\"525\" y=\"215\" />\n" +
                "        <omgdi:waypoint x=\"580\" y=\"215\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_141y7vb_di\" bpmnElement=\"Flow_141y7vb\">\n" +
                "        <omgdi:waypoint x=\"420\" y=\"215\" />\n" +
                "        <omgdi:waypoint x=\"475\" y=\"215\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNEdge id=\"Flow_1owh1r3_di\" bpmnElement=\"Flow_1owh1r3\">\n" +
                "        <omgdi:waypoint x=\"270\" y=\"215\" />\n" +
                "        <omgdi:waypoint x=\"320\" y=\"215\" />\n" +
                "      </bpmndi:BPMNEdge>\n" +
                "      <bpmndi:BPMNShape id=\"BPMNShape_startNode1\" bpmnElement=\"startNode1\" bioc:stroke=\"\">\n" +
                "        <omgdc:Bounds x=\"240\" y=\"200\" width=\"30\" height=\"30\" />\n" +
                "        <bpmndi:BPMNLabel>\n" +
                "          <omgdc:Bounds x=\"242\" y=\"237\" width=\"23\" height=\"14\" />\n" +
                "        </bpmndi:BPMNLabel>\n" +
                "      </bpmndi:BPMNShape>\n" +
                "      <bpmndi:BPMNShape id=\"Activity_0q9xxpo_di\" bpmnElement=\"Activity_0q9xxpo\">\n" +
                "        <omgdc:Bounds x=\"320\" y=\"175\" width=\"100\" height=\"80\" />\n" +
                "      </bpmndi:BPMNShape>\n" +
                "      <bpmndi:BPMNShape id=\"Gateway_1mknj2u_di\" bpmnElement=\"Gateway_1mknj2u\" isMarkerVisible=\"true\">\n" +
                "        <omgdc:Bounds x=\"475\" y=\"190\" width=\"50\" height=\"50\" />\n" +
                "      </bpmndi:BPMNShape>\n" +
                "      <bpmndi:BPMNShape id=\"Activity_0mrl5t4_di\" bpmnElement=\"Activity_0mrl5t4\">\n" +
                "        <omgdc:Bounds x=\"580\" y=\"175\" width=\"100\" height=\"80\" />\n" +
                "      </bpmndi:BPMNShape>\n" +
                "      <bpmndi:BPMNShape id=\"Activity_0u7jnud_di\" bpmnElement=\"Activity_0u7jnud\">\n" +
                "        <omgdc:Bounds x=\"580\" y=\"290\" width=\"100\" height=\"80\" />\n" +
                "      </bpmndi:BPMNShape>\n" +
                "      <bpmndi:BPMNShape id=\"Event_0u2j1il_di\" bpmnElement=\"Event_0u2j1il\">\n" +
                "        <omgdc:Bounds x=\"742\" y=\"197\" width=\"36\" height=\"36\" />\n" +
                "        <bpmndi:BPMNLabel>\n" +
                "          <omgdc:Bounds x=\"748\" y=\"240\" width=\"24\" height=\"14\" />\n" +
                "        </bpmndi:BPMNLabel>\n" +
                "      </bpmndi:BPMNShape>\n" +
                "    </bpmndi:BPMNPlane>\n" +
                "  </bpmndi:BPMNDiagram>\n" +
                "</definitions>\n";
    }

}
